using System;
using System.Diagnostics;
using Community.CsharpSqlite.Entity;

namespace Community.CsharpSqlite
{
    public  class TriggerHelper
    {
        /*
    **
    ** The author disclaims copyright to this source code.  In place of
    ** a legal notice, here is a blessing:
    **
    **    May you do good and not evil.
    **    May you find forgiveness for yourself and forgive others.
    **    May you share freely, never taking more than you give.
    **
    *************************************************************************
    ** This file contains the implementation for TRIGGERs
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2010-03-09 19:31:43 4ae453ea7be69018d8c16eb8dabe05617397dc4d
    **
    **  $Header: Community.CsharpSqlite/src/trigger_c.cs,v 6604176a7dbe 2010/03/12 23:35:36 Noah $
    *************************************************************************
    */
        //#include "sqliteInt.h"

#if !SQLITE_OMIT_TRIGGER
        /*
** Delete a linked list of TriggerStep structures.
*/

        private static void sqlite3DeleteTriggerStep(sqlite3 db, ref TriggerStep pTriggerStep)
        {
            while (pTriggerStep != null)
            {
                TriggerStep pTmp = pTriggerStep;
                pTriggerStep = pTriggerStep.pNext;

                ExprHelper.ExprDelete(db, ref pTmp.pWhere);
                ExprHelper.ExprListDelete(db, ref pTmp.pExprList);
                sqlite3SelectDelete(db, ref pTmp.pSelect);
                Build.IdListDelete(db, ref pTmp.pIdList);

                pTriggerStep = null;
                MemPool.DbFree(db, ref pTmp);
            }
        }

        /*
    ** Given table pTab, return a list of all the triggers attached to
    ** the table. The list is connected by Trigger.pNext pointers.
    **
    ** All of the triggers on pTab that are in the same database as pTab
    ** are already attached to pTab.pTrigger.  But there might be additional
    ** triggers on pTab in the TEMP schema.  This routine prepends all
    ** TEMP triggers on pTab to the beginning of the pTab.pTrigger list
    ** and returns the combined list.
    **
    ** To state it another way:  This routine returns a list of all triggers
    ** that fire off of pTab.  The list will include any TEMP triggers on
    ** pTab as well as the triggers lised in pTab.pTrigger.
    */

        private static Trigger sqlite3TriggerList(Parse pParse, Table pTab)
        {
            Schema pTmpSchema = pParse.db.aDb[1].pSchema;
            Trigger pList = null; /* List of triggers to return */

            if (pParse.disableTriggers != 0)
            {
                return null;
            }

            if (pTmpSchema != pTab.pSchema)
            {
                HashElem p;
                for (p = HashHelper.HashFirst(pTmpSchema.trigHash); p != null; p = HashHelper.HashNext(p))
                {
                    var pTrig = (Trigger) HashHelper.HashData(p);
                    if (pTrig.pTabSchema == pTab.pSchema
                        && 0 == Utility.Sqlite3StrICmp(pTrig.table, pTab.zName)
                        )
                    {
                        pTrig.pNext = (pList != null ? pList : pTab.pTrigger);
                        pList = pTrig;
                    }
                }
            }

            return (pList != null ? pList : pTab.pTrigger);
        }

        /*
    ** This is called by the parser when it sees a CREATE TRIGGER statement
    ** up to the point of the BEGIN before the trigger actions.  A Trigger
    ** structure is generated based on the information available and stored
    ** in pParse.pNewTrigger.  After the trigger actions have been parsed, the
    ** sqlite3FinishTrigger() function is called to complete the trigger
    ** construction process.
    */

        private static void sqlite3BeginTrigger(
            Parse pParse, /* The parse context of the CREATE TRIGGER statement */
            Token pName1, /* The name of the trigger */
            Token pName2, /* The name of the trigger */
            int tr_tm, /* One of TokenKeyword.TK_BEFORE, TokenKeyword.TK_AFTER, TokenKeyword.TK_INSTEAD */
            int op, /* One of TokenKeyword.TK_INSERT, TokenKeyword.TK_UPDATE, TokenKeyword.TK_DELETE */
            IdList pColumns, /* column list if this is an UPDATE OF trigger */
            SrcList pTableName, /* The name of the table/view the trigger applies to */
            Expr pWhen, /* WHEN clause */
            int isTemp, /* True if the TEMPORARY keyword is present */
            int noErr /* Suppress errors if the trigger already exists */
            )
        {
            Trigger pTrigger = null; /* The new trigger */
            Table pTab; /* Table that the trigger fires off of */
            string zName = null; /* Name of the trigger */
            sqlite3 db = pParse.db; /* The database connection */
            int iDb; /* The database to store the trigger in */
            Token pName = null; /* The unqualified db name */
            var sFix = new DbFixer(); /* State vector for the DB fixer */
            int iTabDb; /* Index of the database holding pTab */

            Debug.Assert(pName1 != null); /* pName1.z might be NULL, but not pName1 itself */
            Debug.Assert(pName2 != null);
            Debug.Assert(op == TokenKeyword.TK_INSERT || op == TokenKeyword.TK_UPDATE || op == TokenKeyword.TK_DELETE);
            Debug.Assert(op > 0 && op < 0xff);
            if (isTemp != 0)
            {
                /* If TEMP was specified, then the trigger name may not be qualified. */
                if (pName2.n > 0)
                {
                    Utility.Sqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name");
                    goto trigger_cleanup;
                }
                iDb = 1;
                pName = pName1;
            }
            else
            {
                /* Figure out the db that the the trigger will be created in */
                iDb = Build.TwoPartName(pParse, pName1, pName2, ref pName);
                if (iDb < 0)
                {
                    goto trigger_cleanup;
                }
            }

            /* If the trigger name was unqualified, and the table is a temp table,
      ** then set iDb to 1 to create the trigger in the temporary database.
      ** If sqlite3SrcListLookup() returns 0, indicating the table does not
      ** exist, the error is caught by the block below.
      */
            if (pTableName == null /*|| db.mallocFailed != 0 */)
            {
                goto trigger_cleanup;
            }
            pTab = sqlite3SrcListLookup(pParse, pTableName);
            if (db.init.busy == 0 && pName2.n == 0 && pTab != null
                && pTab.pSchema == db.aDb[1].pSchema)
            {
                iDb = 1;
            }

            /* Ensure the table name matches database name and that the table exists */
//      if ( db.mallocFailed != 0 ) goto trigger_cleanup;
            Debug.Assert(pTableName.nSrc == 1);
            if (AttachHelper.FixInit(sFix, pParse, iDb, "trigger", pName) != 0 &&
                AttachHelper.FixSrcList(sFix, pTableName) != 0)
            {
                goto trigger_cleanup;
            }
            pTab = sqlite3SrcListLookup(pParse, pTableName);
            if (pTab == null)
            {
                /* The table does not exist. */
                if (db.init.iDb == 1)
                {
                    /* Ticket #3810.
          ** Normally, whenever a table is dropped, all associated triggers are
          ** dropped too.  But if a TEMP trigger is created on a non-TEMP table
          ** and the table is dropped by a different database connection, the
          ** trigger is not visible to the database connection that does the
          ** drop so the trigger cannot be dropped.  This results in an
          ** "orphaned trigger" - a trigger whose associated table is missing.
          */
                    db.init.orphanTrigger = 1;
                }
                goto trigger_cleanup;
            }
            if (Utility.IsVirtual(pTab))
            {
                Utility.Sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
                goto trigger_cleanup;
            }

            /* Check that the trigger name is not reserved and that no trigger of the
      ** specified name exists */
            zName = Build.NameFromToken(db, pName);
            if (zName == null || StatusCode.SQLITE_OK != Build.CheckObjectName(pParse, zName))
            {
                goto trigger_cleanup;
            }
            if (HashHelper.HashFind((db.aDb[iDb].pSchema.trigHash),
                                zName, Utility.Sqlite3Strlen30(zName)) != null)
            {
                if (noErr == 0)
                {
                    Utility.Sqlite3ErrorMsg(pParse, "trigger %T already exists", pName);
                }
                goto trigger_cleanup;
            }

            /* Do not create a trigger on a system table */
            if (Utility.Sqlite3StrNICmp(pTab.zName, "sqlite_", 7) == 0)
            {
                Utility.Sqlite3ErrorMsg(pParse, "cannot create trigger on system table");
                pParse.nErr++;
                goto trigger_cleanup;
            }

            /* INSTEAD of triggers are only for views and views only support INSTEAD
      ** of triggers.
      */
            if (pTab.pSelect != null && tr_tm != TokenKeyword.TK_INSTEAD)
            {
                Utility.Sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
                                (tr_tm == TokenKeyword.TK_BEFORE) ? "BEFORE" : "AFTER", pTableName, 0);
                goto trigger_cleanup;
            }
            if (pTab.pSelect == null && tr_tm == TokenKeyword.TK_INSTEAD)
            {
                Utility.Sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF" +
                                        " trigger on table: %S", pTableName, 0);
                goto trigger_cleanup;
            }
            iTabDb = sqlite3SchemaToIndex(db, pTab.pSchema);

#if !SQLITE_OMIT_AUTHORIZATION
{
int code = ActionCode.SQLITE_CREATE_TRIGGER;
string zDb = db.aDb[iTabDb].zName;
string zDbTrig = isTemp ? db.aDb[1].zName : zDb;
if( iTabDb==1 || isTemp ) code = ActionCode.SQLITE_CREATE_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, zName, pTab.zName, zDbTrig) ){
goto trigger_cleanup;
}
if( sqlite3AuthCheck(pParse, ActionCode.SQLITE_INSERT, Helper.SchemaTable(iTabDb),0,zDb)){
goto trigger_cleanup;
}
}
#endif

            /* INSTEAD OF triggers can only appear on views and BEFORE triggers
** cannot appear on views.  So we might as well translate every
** INSTEAD OF trigger into a BEFORE trigger.  It simplifies code
** elsewhere.
*/
            if (tr_tm == TokenKeyword.TK_INSTEAD)
            {
                tr_tm = TokenKeyword.TK_BEFORE;
            }

            /* Build the Trigger object */
            pTrigger = new Trigger(); // (Trigger*)Malloc.DbMallocZero( db, sizeof(Trigger ))
            if (pTrigger == null) goto trigger_cleanup;
            pTrigger.zName = zName;
            pTrigger.table = pTableName.a[0].zName; // sqlite3DbStrDup( db, pTableName.a[0].zName );
            pTrigger.pSchema = db.aDb[iDb].pSchema;
            pTrigger.pTabSchema = pTab.pSchema;
            pTrigger.op = (byte) op;
            pTrigger.tr_tm = tr_tm == TokenKeyword.TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER;
            pTrigger.pWhen = ExprHelper.ExprDup(db, pWhen, Const.EXPRDUP_REDUCE);
            pTrigger.pColumns = ExprHelper.IdListDup(db, pColumns);
            Debug.Assert(pParse.pNewTrigger == null);
            pParse.pNewTrigger = pTrigger;

            trigger_cleanup:
            MemPool.DbFree(db, ref zName);
            Build.SrcListDelete(db, ref pTableName);
            Build.IdListDelete(db, ref pColumns);
            ExprHelper.ExprDelete(db, ref pWhen);
            if (pParse.pNewTrigger == null)
            {
                DeleteTrigger(db, ref pTrigger);
            }
            else
            {
                Debug.Assert(pParse.pNewTrigger == pTrigger);
            }
        }

        /*
    ** This routine is called after all of the trigger actions have been parsed
    ** in order to complete the process of building the trigger.
    */

        private static void sqlite3FinishTrigger(
            Parse pParse, /* Parser context */
            TriggerStep pStepList, /* The triggered program */
            Token pAll /* Token that describes the complete CREATE TRIGGER */
            )
        {
            Trigger pTrig = pParse.pNewTrigger; /* Trigger being finished */
            string zName; /* Name of trigger */

            sqlite3 db = pParse.db; /* The database */
            var sFix = new DbFixer(); /* Fixer object */
            int iDb; /* Database containing the trigger */
            var nameToken = new Token(); /* Trigger name for error reporting */

            pTrig = pParse.pNewTrigger;
            pParse.pNewTrigger = null;
            if (UnitTest.NEVER(pParse.nErr != 0) || pTrig == null) goto triggerfinish_cleanup;
            zName = pTrig.zName;
            iDb = sqlite3SchemaToIndex(pParse.db, pTrig.pSchema);
            pTrig.step_list = pStepList;
            while (pStepList != null)
            {
                pStepList.pTrig = pTrig;
                pStepList = pStepList.pNext;
            }
            nameToken.z = pTrig.zName;
            nameToken.n = Utility.Sqlite3Strlen30(nameToken.z);
            if (AttachHelper.FixInit(sFix, pParse, iDb, "trigger", nameToken) != 0
                && AttachHelper.FixTriggerStep(sFix, pTrig.step_list) != 0)
            {
                goto triggerfinish_cleanup;
            }

            /* if we are not initializing,
      ** build the sqlite_master entry
      */
            if (0 == db.init.busy)
            {
                Vdbe v;
                string z;

                /* Make an entry in the sqlite_master table */
                v = SelectHelper.GetVdbe(pParse);
                if (v == null) goto triggerfinish_cleanup;
                Build.BeginWriteOperation(pParse, 0, iDb);
                z = pAll.z.Substring(0, pAll.n); //sqlite3DbStrNDup( db, (char*)pAll.z, pAll.n );
                Build.NestedParse(pParse,
                                   "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')",
                                   db.aDb[iDb].zName, Helper.SchemaTable(iDb), zName,
                                   pTrig.table, z);
                MemPool.DbFree(db, ref z);
                Build.ChangeCookie(pParse, iDb);
                VdbeAux.VdbeAddOp4(v, OPCode.OP_ParseSchema, iDb, 0, 0, Print.MPrintf(
                    db, "type='trigger' AND name='%q'", zName), P4Type.P4_DYNAMIC
                    );
            }

            if (db.init.busy != 0)
            {
                Trigger pLink = pTrig;
                Hash pHash = db.aDb[iDb].pSchema.trigHash;
                pTrig = (Trigger) HashHelper.HashInsert(ref pHash, zName, Utility.Sqlite3Strlen30(zName), pTrig);
                if (pTrig != null)
                {
                    //db.mallocFailed = 1;
                }
                else if (pLink.pSchema == pLink.pTabSchema)
                {
                    Table pTab;
                    int n = Utility.Sqlite3Strlen30(pLink.table);
                    pTab = (Table) HashHelper.HashFind(pLink.pTabSchema.tblHash, pLink.table, n);
                    Debug.Assert(pTab != null);
                    pLink.pNext = pTab.pTrigger;
                    pTab.pTrigger = pLink;
                }
            }

            triggerfinish_cleanup:
            DeleteTrigger(db, ref pTrig);
            Debug.Assert(pParse.pNewTrigger == null);
            sqlite3DeleteTriggerStep(db, ref pStepList);
        }

        /*
    ** Turn a SELECT statement (that the pSelect parameter points to) into
    ** a trigger step.  Return a pointer to a TriggerStep structure.
    **
    ** The parser calls this routine when it finds a SELECT statement in
    ** body of a TRIGGER.
    */

        private static TriggerStep sqlite3TriggerSelectStep(sqlite3 db, Select pSelect)
        {
            var pTriggerStep = new TriggerStep(); // Malloc.DbMallocZero( db, sizeof(TriggerStep ))
            if (pTriggerStep == null)
            {
                sqlite3SelectDelete(db, ref pSelect);
                return null;
            }

            pTriggerStep.op = TokenKeyword.TK_SELECT;
            pTriggerStep.pSelect = pSelect;
            pTriggerStep.orconf = OnConstraintError.OE_Default;
            return pTriggerStep;
        }

        /*
    ** Allocate space to hold a new trigger step.  The allocated space
    ** holds both the TriggerStep object and the TriggerStep.target.z string.
    **
    ** If an OOM error occurs, NULL is returned and db.mallocFailed is set.
    */

        private static TriggerStep triggerStepAllocate(
            sqlite3 db, /* Database connection */
            byte op, /* Trigger opcode */
            Token pName /* The target name */
            )
        {
            TriggerStep pTriggerStep;

            pTriggerStep = new TriggerStep(); // Malloc.DbMallocZero( db, sizeof( TriggerStep ) + pName.n );
            //if ( pTriggerStep != null )
            //{
            string z; // = (char*)&pTriggerStep[1];
            z = pName.z; // memcpy( z, pName.z, pName.n );
            pTriggerStep.target.z = z;
            pTriggerStep.target.n = pName.n;
            pTriggerStep.op = op;
            //}
            return pTriggerStep;
        }

        /*
    ** Build a trigger step out of an INSERT statement.  Return a pointer
    ** to the new trigger step.
    **
    ** The parser calls this routine when it sees an INSERT inside the
    ** body of a trigger.
    */
        // OVERLOADS, so I don't need to rewrite parse.c
        private static TriggerStep sqlite3TriggerInsertStep(sqlite3 db, Token pTableName, IdList pColumn, int null_4,
                                                            int null_5, byte orconf)
        {
            return sqlite3TriggerInsertStep(db, pTableName, pColumn, null, null, orconf);
        }

        private static TriggerStep sqlite3TriggerInsertStep(sqlite3 db, Token pTableName, IdList pColumn,
                                                            ExprList pEList, int null_5, byte orconf)
        {
            return sqlite3TriggerInsertStep(db, pTableName, pColumn, pEList, null, orconf);
        }

        private static TriggerStep sqlite3TriggerInsertStep(sqlite3 db, Token pTableName, IdList pColumn, int null_4,
                                                            Select pSelect, byte orconf)
        {
            return sqlite3TriggerInsertStep(db, pTableName, pColumn, null, pSelect, orconf);
        }

        private static TriggerStep sqlite3TriggerInsertStep(
            sqlite3 db, /* The database connection */
            Token pTableName, /* Name of the table into which we insert */
            IdList pColumn, /* List of columns in pTableName to insert into */
            ExprList pEList, /* The VALUE clause: a list of values to be inserted */
            Select pSelect, /* A SELECT statement that supplies values */
            byte orconf /* The conflict algorithm (OnConstraintError.OE_Abort, OnConstraintError.OE_Replace, etc.) */
            )
        {
            TriggerStep pTriggerStep;

            Debug.Assert(pEList == null || pSelect == null);
            Debug.Assert(pEList != null || pSelect != null /*|| db.mallocFailed != 0 */);

            pTriggerStep = triggerStepAllocate(db, TokenKeyword.TK_INSERT, pTableName);
            //if ( pTriggerStep != null )
            //{
            pTriggerStep.pSelect = ExprHelper.SelectDup(db, pSelect, Const.EXPRDUP_REDUCE);
            pTriggerStep.pIdList = pColumn;
            pTriggerStep.pExprList = ExprHelper.ExprListDup(db, pEList, Const.EXPRDUP_REDUCE);
            pTriggerStep.orconf = orconf;
            //}
            //else
            //{
            //  Build.IdListDelete( db, ref pColumn );
            //}
            ExprHelper.ExprListDelete(db, ref pEList);
            sqlite3SelectDelete(db, ref pSelect);

            return pTriggerStep;
        }

        /*
    ** Construct a trigger step that implements an UPDATE statement and return
    ** a pointer to that trigger step.  The parser calls this routine when it
    ** sees an UPDATE statement inside the body of a CREATE TRIGGER.
    */

        private static TriggerStep sqlite3TriggerUpdateStep(
            sqlite3 db, /* The database connection */
            Token pTableName, /* Name of the table to be updated */
            ExprList pEList, /* The SET clause: list of column and new values */
            Expr pWhere, /* The WHERE clause */
            byte orconf /* The conflict algorithm. (OnConstraintError.OE_Abort, OnConstraintError.OE_Ignore, etc) */
            )
        {
            TriggerStep pTriggerStep;

            pTriggerStep = triggerStepAllocate(db, TokenKeyword.TK_UPDATE, pTableName);
            //if ( pTriggerStep != null )
            //{
            pTriggerStep.pExprList = ExprHelper.ExprListDup(db, pEList, Const.EXPRDUP_REDUCE);
            pTriggerStep.pWhere = ExprHelper.ExprDup(db, pWhere, Const.EXPRDUP_REDUCE);
            pTriggerStep.orconf = orconf;
            //}
            ExprHelper.ExprListDelete(db, ref pEList);
            ExprHelper.ExprDelete(db, ref pWhere);
            return pTriggerStep;
        }

        /*
    ** Construct a trigger step that implements a DELETE statement and return
    ** a pointer to that trigger step.  The parser calls this routine when it
    ** sees a DELETE statement inside the body of a CREATE TRIGGER.
    */

        private static TriggerStep sqlite3TriggerDeleteStep(
            sqlite3 db, /* Database connection */
            Token pTableName, /* The table from which rows are deleted */
            Expr pWhere /* The WHERE clause */
            )
        {
            TriggerStep pTriggerStep;

            pTriggerStep = triggerStepAllocate(db, TokenKeyword.TK_DELETE, pTableName);
            //if ( pTriggerStep != null )
            //{
            pTriggerStep.pWhere = ExprHelper.ExprDup(db, pWhere, Const.EXPRDUP_REDUCE);
            pTriggerStep.orconf = OnConstraintError.OE_Default;
            //}
            ExprHelper.ExprDelete(db, ref pWhere);
            return pTriggerStep;
        }



        /// <summary>
        ///  Recursively delete a Trigger structure
        /// </summary>

        public static void DeleteTrigger(sqlite3 db, ref Trigger pTrigger)
        {
            if (pTrigger == null) return;
            sqlite3DeleteTriggerStep(db, ref pTrigger.step_list);
            MemPool.DbFree(db, ref pTrigger.zName);
            MemPool.DbFree(db, ref pTrigger.table);
            ExprHelper.ExprDelete(db, ref pTrigger.pWhen);
            Build.IdListDelete(db, ref pTrigger.pColumns);
            pTrigger = null;
            MemPool.DbFree(db, ref pTrigger);
        }

        /*
    ** This function is called to drop a trigger from the database schema.
    **
    ** This may be called directly from the parser and therefore identifies
    ** the trigger by name.  The sqlite3DropTriggerPtr() routine does the
    ** same job as this routine except it takes a pointer to the trigger
    ** instead of the trigger name.
    **/

        private static void sqlite3DropTrigger(Parse pParse, SrcList pName, int noErr)
        {
            Trigger pTrigger = null;
            int i;
            string zDb;
            string zName;
            int nName;
            sqlite3 db = pParse.db;

//      if ( db.mallocFailed != 0 ) goto drop_trigger_cleanup;
            if (StatusCode.SQLITE_OK != Prepare.ReadSchema(pParse))
            {
                goto drop_trigger_cleanup;
            }

            Debug.Assert(pName.nSrc == 1);
            zDb = pName.a[0].zDatabase;
            zName = pName.a[0].zName;
            nName = Utility.Sqlite3Strlen30(zName);
            for (i = Const.OMIT_TEMPDB; i < db.nDb; i++)
            {
                int j = (i < 2) ? i ^ 1 : i; /* Search TEMP before MAIN */
                if (zDb != null && Utility.Sqlite3StrICmp(db.aDb[j].zName, zDb) != 0) continue;
                pTrigger = (Trigger) HashHelper.HashFind((db.aDb[j].pSchema.trigHash), zName, nName);
                if (pTrigger != null) break;
            }
            if (pTrigger == null)
            {
                if (noErr == 0)
                {
                    Utility.Sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
                }
                goto drop_trigger_cleanup;
            }
            sqlite3DropTriggerPtr(pParse, pTrigger);

            drop_trigger_cleanup:
            Build.SrcListDelete(db, ref pName);
        }

        /*
    ** Return a pointer to the Table structure for the table that a trigger
    ** is set on.
    */

        private static Table tableOfTrigger(Trigger pTrigger)
        {
            int n = Utility.Sqlite3Strlen30(pTrigger.table);
            return (Table) HashHelper.HashFind(pTrigger.pTabSchema.tblHash, pTrigger.table, n);
        }


        /*
    ** Drop a trigger given a pointer to that trigger.
    */

        private static void sqlite3DropTriggerPtr(Parse pParse, Trigger pTrigger)
        {
            Table pTable;
            Vdbe v;
            sqlite3 db = pParse.db;
            int iDb;

            iDb = sqlite3SchemaToIndex(pParse.db, pTrigger.pSchema);
            Debug.Assert(iDb >= 0 && iDb < db.nDb);
            pTable = tableOfTrigger(pTrigger);
            Debug.Assert(pTable != null);
            Debug.Assert(pTable.pSchema == pTrigger.pSchema || iDb == 1);
#if !SQLITE_OMIT_AUTHORIZATION
{
int code = SQLITE_DROPCode.OP_TRIGGER;
string zDb = db.aDb[iDb].zName;
string zTab = Helper.SchemaTable(iDb);
if( iDb==1 ) code = SQLITE_DROPCode.OP_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, pTrigger.name, pTable.zName, zDb) ||
sqlite3AuthCheck(pParse, ActionCode.SQLITE_DELETE, zTab, 0, zDb) ){
return;
}
}
#endif

            /* Generate code to destroy the database record of the trigger.
*/
            Debug.Assert(pTable != null);
            if ((v = SelectHelper.GetVdbe(pParse)) != null)
            {
                int _base;
                var dropTrigger = new[]
                                      {
                                          new VdbeOpList(OPCode.OP_Rewind, 0, Utility.Addr(9), 0),
                                          new VdbeOpList(OPCode.OP_String8, 0, 1, 0), /* 1 */
                                          new VdbeOpList(OPCode.OP_Column, 0, 1, 2),
                                          new VdbeOpList(OPCode.OP_Ne, 2, Utility.Addr(8), 1),
                                          new VdbeOpList(OPCode.OP_String8, 0, 1, 0), /* 4: "trigger" */
                                          new VdbeOpList(OPCode.OP_Column, 0, 0, 2),
                                          new VdbeOpList(OPCode.OP_Ne, 2, Utility.Addr(8), 1),
                                          new VdbeOpList(OPCode.OP_Delete, 0, 0, 0),
                                          new VdbeOpList(OPCode.OP_Next, 0, Utility.Addr(1), 0), /* 8 */
                                      };

                Build.BeginWriteOperation(pParse, 0, iDb);
                Build.OpenMasterTable(pParse, iDb);
                _base = VdbeAux.VdbeAddOpList(v, dropTrigger.Length, dropTrigger);
                VdbeAux.VdbeChangeP4(v, _base + 1, pTrigger.zName, 0);
                VdbeAux.VdbeChangeP4(v, _base + 4, "trigger", P4Type.P4_STATIC);
                Build.ChangeCookie(pParse, iDb);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, 0, 0);
                VdbeAux.VdbeAddOp4(v, OPCode.OP_DropTrigger, iDb, 0, 0, pTrigger.zName, 0);
                if (pParse.nMem < 3)
                {
                    pParse.nMem = 3;
                }
            }
        }

        /*
    ** Remove a trigger from the hash tables of the sqlite* pointer.
    */

        private static void sqlite3UnlinkAndDeleteTrigger(sqlite3 db, int iDb, string zName)
        {
            Hash pHash = db.aDb[iDb].pSchema.trigHash;
            Trigger pTrigger;
            pTrigger = (Trigger) HashHelper.HashInsert(ref pHash, zName, Utility.Sqlite3Strlen30(zName), null);
            if (UnitTest.ALWAYS(pTrigger != null))
            {
                if (pTrigger.pSchema == pTrigger.pTabSchema)
                {
                    Table pTab = tableOfTrigger(pTrigger);
                    //Trigger** pp;
                    //for ( pp = &pTab.pTrigger ; *pp != pTrigger ; pp = &( (*pp).pNext ) ) ;
                    //*pp = (*pp).pNext;
                    if (pTab.pTrigger == pTrigger)
                    {
                        pTab.pTrigger = pTrigger.pNext;
                    }
                    else
                    {
                        Trigger cc = pTab.pTrigger;
                        while (cc != null)
                        {
                            if (cc.pNext == pTrigger)
                            {
                                cc.pNext = cc.pNext.pNext;
                                break;
                            }
                            cc = cc.pNext;
                        }
                        Debug.Assert(cc != null);
                    }
                }
                DeleteTrigger(db, ref pTrigger);
                db.flags |= Flag.SQLITE_InternChanges;
            }
        }

        /*
    ** pEList is the SET clause of an UPDATE statement.  Each entry
    ** in pEList is of the format <id>=<expr>.  If any of the entries
    ** in pEList have an <id> which matches an identifier in pIdList,
    ** then return TRUE.  If pIdList==NULL, then it is considered a
    ** wildcard that matches anything.  Likewise if pEList==NULL then
    ** it matches anything so always return true.  Return false only
    ** if there is no match.
    */

        private static int checkColumnOverlap(IdList pIdList, ExprList pEList)
        {
            int e;
            if (pIdList == null || UnitTest.NEVER(pEList == null)) return 1;
            for (e = 0; e < pEList.nExpr; e++)
            {
                if (Build.IdListIndex(pIdList, pEList.a[e].zName) >= 0) return 1;
            }
            return 0;
        }

        /*
    ** Return a list of all triggers on table pTab if there exists at least
    ** one trigger that must be fired when an operation of type 'op' is
    ** performed on the table, and, if that operation is an UPDATE, if at
    ** least one of the columns in pChanges is being modified.
    */

        private static Trigger sqlite3TriggersExist(
            Parse pParse, /* Parse context */
            Table pTab, /* The table the contains the triggers */
            int op, /* one of TokenKeyword.TK_DELETE, TokenKeyword.TK_INSERT, TokenKeyword.TK_UPDATE */
            ExprList pChanges, /* Columns that change in an UPDATE statement */
            ref int pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
            )
        {
            int mask = 0;
            Trigger pList = sqlite3TriggerList(pParse, pTab);
            Trigger p;
            Debug.Assert(pList == null || Utility.IsVirtual(pTab) == false);
            for (p = pList; p != null; p = p.pNext)
            {
                if (p.op == op && checkColumnOverlap(p.pColumns, pChanges) != 0)
                {
                    mask |= p.tr_tm;
                }
            }
            //if ( pMask != 0 )
            {
                pMask = mask;
            }
            return (mask != 0 ? pList : null);
        }


        /*
    ** Convert the pStep.target token into a SrcList and return a pointer
    ** to that SrcList.
    **
    ** This routine adds a specific database name, if needed, to the target when
    ** forming the SrcList.  This prevents a trigger in one database from
    ** referring to a target in another database.  An exception is when the
    ** trigger is in TEMP in which case it can refer to any other database it
    ** wants.
    */

        private static SrcList targetSrcList(
            Parse pParse, /* The parsing context */
            TriggerStep pStep /* The trigger containing the target token */
            )
        {
            int iDb; /* Index of the database to use */
            SrcList pSrc; /* SrcList to be returned */

            pSrc = Build.SrcListAppend(pParse.db, 0, pStep.target, 0);
            //if ( pSrc != null )
            //{
            Debug.Assert(pSrc.nSrc > 0);
            Debug.Assert(pSrc.a != null);
            iDb = sqlite3SchemaToIndex(pParse.db, pStep.pTrig.pSchema);
            if (iDb == 0 || iDb >= 2)
            {
                sqlite3 db = pParse.db;
                Debug.Assert(iDb < pParse.db.nDb);
                pSrc.a[pSrc.nSrc - 1].zDatabase = db.aDb[iDb].zName; // sqlite3DbStrDup( db, db.aDb[iDb].zName );
            }
            //}
            return pSrc;
        }

        /*
    ** Generate VDBE code for the statements inside the body of a single 
    ** trigger.
    */

        private static int codeTriggerProgram(
            Parse pParse, /* The parser context */
            TriggerStep pStepList, /* List of statements inside the trigger body */
            int orconf /* Conflict algorithm. (OnConstraintError.OE_Abort, etc) */
            )
        {
            TriggerStep pStep;
            Vdbe v = pParse.pVdbe;
            sqlite3 db = pParse.db;

            Debug.Assert(pParse.pTriggerTab != null && pParse.pToplevel != null);
            Debug.Assert(pStepList != null);
            Debug.Assert(v != null);
            for (pStep = pStepList; pStep != null; pStep = pStep.pNext)
            {
                /* Figure out the ON CONFLICT policy that will be used for this step
    ** of the trigger program. If the statement that caused this trigger
    ** to fire had an explicit ON CONFLICT, then use it. Otherwise, use
    ** the ON CONFLICT policy that was specified as part of the trigger
    ** step statement. Example:
    **
    **   CREATE TRIGGER AFTER INSERT ON t1 BEGIN;
    **     INSERT OR REPLACE INTO t2 VALUES(new.a, new.b);
    **   END;
    **
    **   INSERT INTO t1 ... ;            -- insert into t2 uses REPLACE policy
    **   INSERT OR IGNORE INTO t1 ... ;  -- insert into t2 uses IGNORE policy
    */
                pParse.eOrconf = (orconf == OnConstraintError.OE_Default) ? pStep.orconf : (byte) orconf;

                switch (pStep.op)
                {
                    case TokenKeyword.TK_UPDATE:
                        {
                            sqlite3Update(pParse,
                                          targetSrcList(pParse, pStep),
                                          ExprHelper.ExprListDup(db, pStep.pExprList, 0),
                                          ExprHelper.ExprDup(db, pStep.pWhere, 0),
                                          pParse.eOrconf
                                );
                            break;
                        }
                    case TokenKeyword.TK_INSERT:
                        {
                            sqlite3Insert(pParse,
                                          targetSrcList(pParse, pStep),
                                          ExprHelper.ExprListDup(db, pStep.pExprList, 0),
                                          ExprHelper.SelectDup(db, pStep.pSelect, 0),
                                          ExprHelper.IdListDup(db, pStep.pIdList),
                                          pParse.eOrconf
                                );
                            break;
                        }
                    case TokenKeyword.TK_DELETE:
                        {
                            sqlite3DeleteFrom(pParse,
                                              targetSrcList(pParse, pStep),
                                              ExprHelper.ExprDup(db, pStep.pWhere, 0)
                                );
                            break;
                        }
                    default:
                        Debug.Assert(pStep.op == TokenKeyword.TK_SELECT);
                        {
                            var sDest = new SelectDest();
                            Select pSelect = ExprHelper.SelectDup(db, pStep.pSelect, 0);
                            sqlite3SelectDestInit(sDest, SelectResultType.SRT_Discard, 0);
                            sqlite3Select(pParse, pSelect, ref sDest);
                            sqlite3SelectDelete(db, ref pSelect);
                            break;
                        }
                }
                if (pStep.op != TokenKeyword.TK_SELECT)
                {
                    VdbeAux.VdbeAddOp0(v, OPCode.OP_ResetCount);
                }
            }

            return 0;
        }

#if SQLITE_DEBUG
/*
** This function is used to add VdbeComment() annotations to a VDBE
** program. It is not used in production code, only for debugging.
*/

        private static string onErrorText(int onError)
        {
            switch (onError)
            {
                case OnConstraintError.OE_Abort:
                    return "abort";
                case OnConstraintError.OE_Rollback:
                    return "rollback";
                case OnConstraintError.OE_Fail:
                    return "fail";
                case OnConstraintError.OE_Replace:
                    return "replace";
                case OnConstraintError.OE_Ignore:
                    return "ignore";
                case OnConstraintError.OE_Default:
                    return "default";
            }
            return "n/a";
        }
#endif

/*
** Parse context structure pFrom has just been used to create a sub-vdbe
** (trigger program). If an error has occurred, transfer error information
** from pFrom to pTo.
*/

        private static void transferParseError(Parse pTo, Parse pFrom)
        {
            Debug.Assert(String.IsNullOrEmpty(pFrom.zErrMsg) || pFrom.nErr != 0);
            Debug.Assert(String.IsNullOrEmpty(pTo.zErrMsg) || pTo.nErr != 0);
            if (pTo.nErr == 0)
            {
                pTo.zErrMsg = pFrom.zErrMsg;
                pTo.nErr = pFrom.nErr;
            }
            else
            {
                MemPool.DbFree(pFrom.db, ref pFrom.zErrMsg);
            }
        }

/*
** Create and populate a new TriggerPrg object with a sub-program 
** implementing trigger pTrigger with ON CONFLICT policy orconf.
*/

        private static TriggerPrg codeRowTrigger(
            Parse pParse, /* Current parse context */
            Trigger pTrigger, /* Trigger to code */
            Table pTab, /* The table pTrigger is attached to */
            int orconf /* ON CONFLICT policy to code trigger program with */
            )
        {
            Parse pTop = sqlite3ParseToplevel(pParse);
            sqlite3 db = pParse.db; /* Database handle */
            TriggerPrg pPrg; /* Value to return */
            Expr pWhen = null; /* Duplicate of trigger WHEN expression */
            Vdbe v; /* Temporary VM */
            NameContext sNC; /* Name context for sub-vdbe */
            SubProgram pProgram = null; /* Sub-vdbe for trigger program */
            Parse pSubParse; /* Parse context for sub-vdbe */
            int iEndTrigger = 0; /* Label to jump to if WHEN is false */

            Debug.Assert(pTrigger.zName == null || pTab == tableOfTrigger(pTrigger));

            /* Allocate the TriggerPrg and SubProgram objects. To ensure that they
  ** are freed if an error occurs, link them into the Parse.pTriggerPrg 
  ** list of the top-level Parse object sooner rather than later.  */
            pPrg = new TriggerPrg(); // Malloc.DbMallocZero( db, sizeof( TriggerPrg ) );
            //if ( null == pPrg ) return 0;
            pPrg.pNext = pTop.pTriggerPrg;
            pTop.pTriggerPrg = pPrg;
            pPrg.pProgram = pProgram = new SubProgram(); // Malloc.DbMallocZero( db, sizeof( SubProgram ) );
            //if( null==pProgram ) return 0;
            pProgram.nRef = 1;
            pPrg.pTrigger = pTrigger;
            pPrg.orconf = orconf;
            pPrg.aColmask[0] = 0xffffffff;
            pPrg.aColmask[1] = 0xffffffff;


            /* Allocate and populate a new Parse context to use for coding the 
  ** trigger sub-program.  */
            pSubParse = new Parse(); // sqlite3StackAllocZero( db, sizeof( Parse ) );
            //if ( null == pSubParse ) return null;
            sNC = new NameContext(); // memset( &sNC, 0, sizeof( sNC ) );
            sNC.pParse = pSubParse;
            pSubParse.db = db;
            pSubParse.pTriggerTab = pTab;
            pSubParse.pToplevel = pTop;
            pSubParse.zAuthContext = pTrigger.zName;
            pSubParse.eTriggerOp = pTrigger.op;

            v = SelectHelper.GetVdbe(pSubParse);
            if (v != null)
            {
#if SQLITE_DEBUG
                VdbeComment(v, "Start: %s.%s (%s %s%s%s ON %s)",
                            pTrigger.zName, onErrorText(orconf),
                            (pTrigger.tr_tm == TRIGGER_BEFORE ? "BEFORE" : "AFTER"),
                            (pTrigger.op == TokenKeyword.TK_UPDATE ? "UPDATE" : ""),
                            (pTrigger.op == TokenKeyword.TK_INSERT ? "INSERT" : ""),
                            (pTrigger.op == TokenKeyword.TK_DELETE ? "DELETE" : ""),
                            pTab.zName
                    );
#endif
#if !SQLITE_OMIT_TRACE
                VdbeAux.VdbeChangeP4(v, -1,
                                    Print.MPrintf(db, "-- TRIGGER %s", pTrigger.zName), P4Type.P4_DYNAMIC
                    );
#endif

                /* If one was specified, code the WHEN clause. If it evaluates to false
    ** (or NULL) the sub-vdbe is immediately halted by jumping to the 
    ** OPCode.OP_Halt inserted at the end of the program.  */
                if (pTrigger.pWhen != null)
                {
                    pWhen = ExprHelper.ExprDup(db, pTrigger.pWhen, 0);
                    if (StatusCode.SQLITE_OK == sqlite3ResolveExprNames(sNC, ref pWhen) 
                        //&& db.mallocFailed==0 
                        )
                    {
                        iEndTrigger = VdbeAux.VdbeMakeLabel(v);
                        ExprHelper.ExprIfFalse(pSubParse, pWhen, iEndTrigger, Const.SQLITE_JUMPIFNULL);
                    }
                    ExprHelper.ExprDelete(db, ref pWhen);
                }

                /* Code the trigger program into the sub-vdbe. */
                codeTriggerProgram(pSubParse, pTrigger.step_list, orconf);

                /* Insert an OPCode.OP_Halt at the end of the sub-program. */
                if (iEndTrigger != 0)
                {
                    VdbeAux.VdbeResolveLabel(v, iEndTrigger);
                }
                VdbeAux.VdbeAddOp0(v, OPCode.OP_Halt);
#if SQLITE_DEBUG
                VdbeComment(v, "End: %s.%s", pTrigger.zName, onErrorText(orconf));
#endif
                transferParseError(pParse, pSubParse);
                //if( db.mallocFailed==0 ){
                pProgram.aOp = VdbeAux.VdbeTakeOpArray(v, ref pProgram.nOp, ref pTop.nMaxArg);
                //}
                pProgram.nMem = pSubParse.nMem;
                pProgram.nCsr = pSubParse.nTab;
                pProgram.token = pTrigger.GetHashCode();
                pPrg.aColmask[0] = pSubParse.oldmask;
                pPrg.aColmask[1] = pSubParse.newmask;
                VdbeAux.VdbeDelete(ref v);
            }

            Debug.Assert(null == pSubParse.pAinc && null == pSubParse.pZombieTab);
            Debug.Assert(null == pSubParse.pTriggerPrg && 0 == pSubParse.nMaxArg);
            //sqlite3StackFree(db, pSubParse);

            return pPrg;
        }

/*
** Return a pointer to a TriggerPrg object containing the sub-program for
** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such
** TriggerPrg object exists, a new object is allocated and populated before
** being returned.
*/

        private static TriggerPrg getRowTrigger(
            Parse pParse, /* Current parse context */
            Trigger pTrigger, /* Trigger to code */
            Table pTab, /* The table trigger pTrigger is attached to */
            int orconf /* ON CONFLICT algorithm. */
            )
        {
            Parse pRoot = sqlite3ParseToplevel(pParse);
            TriggerPrg pPrg;

            Debug.Assert(pTrigger.zName == null || pTab == tableOfTrigger(pTrigger));

            /* It may be that this trigger has already been coded (or is in the
  ** process of being coded). If this is the case, then an entry with
  ** a matching TriggerPrg.pTrigger field will be present somewhere
  ** in the Parse.pTriggerPrg list. Search for such an entry.  */
            for (pPrg = pRoot.pTriggerPrg;
                 pPrg != null && (pPrg.pTrigger != pTrigger || pPrg.orconf != orconf);
                 pPrg = pPrg.pNext
                ) ;

            /* If an existing TriggerPrg could not be located, create a new one. */
            if (null == pPrg)
            {
                pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf);
            }

            return pPrg;
        }

/*
** Generate code for the trigger program associated with trigger p on 
** table pTab. The reg, orconf and ignoreJump parameters passed to this
** function are the same as those described in the header function for
** sqlite3CodeRowTrigger()
*/

        private static void sqlite3CodeRowTriggerDirect(
            Parse pParse, /* Parse context */
            Trigger p, /* Trigger to code */
            Table pTab, /* The table to code triggers from */
            int reg, /* Reg array containing OLD.* and NEW.* values */
            int orconf, /* ON CONFLICT policy */
            int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */
            )
        {
            Vdbe v = SelectHelper.GetVdbe(pParse); /* Main VM */
            TriggerPrg pPrg;
            pPrg = getRowTrigger(pParse, p, pTab, orconf);
            Debug.Assert(pPrg != null || pParse.nErr != 0); //|| pParse.db.mallocFailed );

            /* Code the OPCode.OP_Program opcode in the parent VDBE. P4 of the OPCode.OP_Program 
  ** is a pointer to the sub-vdbe containing the trigger program.  */
            if (pPrg != null)
            {
                VdbeAux.VdbeAddOp3(v, OPCode.OP_Program, reg, ignoreJump, ++pParse.nMem);
                pPrg.pProgram.nRef++;
                VdbeAux.VdbeChangeP4(v, -1, pPrg.pProgram, P4Type.P4_SUBPROGRAM);
#if SQLITE_DEBUG
                VdbeComment
                    (v, "Call: %s.%s", (!String.IsNullOrEmpty(p.zName) ? p.zName : "fkey"), onErrorText(orconf));
#endif

                /* Set the P5 operand of the OPCode.OP_Program instruction to non-zero if
    ** recursive invocation of this trigger program is disallowed. Recursive
    ** invocation is disallowed if (a) the sub-program is really a trigger,
    ** not a foreign key action, and (b) the flag to enable recursive triggers
    ** is clear.  */
                VdbeAux.VdbeChangeP5(v,
                                    (byte)
                                    (!String.IsNullOrEmpty(p.zName) && 0 == (pParse.db.flags & Flag.SQLITE_RecTriggers)
                                         ? 1
                                         : 0));
            }
        }

/*
** This is called to code the required FOR EACH ROW triggers for an operation
** on table pTab. The operation to code triggers for (INSERT, UPDATE or DELETE)
** is given by the op paramater. The tr_tm parameter determines whether the
** BEFORE or AFTER triggers are coded. If the operation is an UPDATE, then
** parameter pChanges is passed the list of columns being modified.
**
** If there are no triggers that fire at the specified time for the specified
** operation on pTab, this function is a no-op.
**
** The reg argument is the address of the first in an array of registers 
** that contain the values substituted for the new.* and old.* references
** in the trigger program. If N is the number of columns in table pTab
** (a copy of pTab.nCol), then registers are populated as follows:
**
**   Register       Contains
**   ------------------------------------------------------
**   reg+0          OLD.rowid
**   reg+1          OLD.* value of left-most column of pTab
**   ...            ...
**   reg+N          OLD.* value of right-most column of pTab
**   reg+N+1        NEW.rowid
**   reg+N+2        OLD.* value of left-most column of pTab
**   ...            ...
**   reg+N+N+1      NEW.* value of right-most column of pTab
**
** For ON DELETE triggers, the registers containing the NEW.* values will
** never be accessed by the trigger program, so they are not allocated or 
** populated by the caller (there is no data to populate them with anyway). 
** Similarly, for ON INSERT triggers the values stored in the OLD.* registers
** are never accessed, and so are not allocated by the caller. So, for an
** ON INSERT trigger, the value passed to this function as parameter reg
** is not a readable register, although registers (reg+N) through 
** (reg+N+N+1) are.
**
** Parameter orconf is the default conflict resolution algorithm for the
** trigger program to use (REPLACE, IGNORE etc.). Parameter ignoreJump
** is the instruction that control should jump to if a trigger program
** raises an IGNORE exception.
*/

        private static void sqlite3CodeRowTrigger(
            Parse pParse, /* Parse context */
            Trigger pTrigger, /* List of triggers on table pTab */
            int op, /* One of TokenKeyword.TK_UPDATE, TokenKeyword.TK_INSERT, TokenKeyword.TK_DELETE */
            ExprList pChanges, /* Changes list for any UPDATE OF triggers */
            int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */
            Table pTab, /* The table to code triggers from */
            int reg, /* The first in an array of registers (see above) */
            int orconf, /* ON CONFLICT policy */
            int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */
            )
        {
            Trigger p; /* Used to iterate through pTrigger list */

            Debug.Assert(op == TokenKeyword.TK_UPDATE || op == TokenKeyword.TK_INSERT || op == TokenKeyword.TK_DELETE);
            Debug.Assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER);
            Debug.Assert((op == TokenKeyword.TK_UPDATE) == (pChanges != null));

            for (p = pTrigger; p != null; p = p.pNext)
            {
                /* Sanity checking:  The schema for the trigger and for the table are
    ** always defined.  The trigger must be in the same schema as the table
    ** or else it must be a TEMP trigger. */
                Debug.Assert(p.pSchema != null);
                Debug.Assert(p.pTabSchema != null);
                Debug.Assert(p.pSchema == p.pTabSchema
                             || p.pSchema == pParse.db.aDb[1].pSchema);

                /* Determine whether we should code this trigger */
                if (p.op == op
                    && p.tr_tm == tr_tm
                    && checkColumnOverlap(p.pColumns, pChanges) != 0
                    )
                {
                    sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
                }
            }
        }

/*
** Triggers may access values stored in the old.* or new.* pseudo-table. 
** This function returns a 32-bit bitmask indicating which columns of the 
** old.* or new.* tables actually are used by triggers. This information 
** may be used by the caller, for example, to avoid having to load the entire
** old.* record into memory when executing an UPDATE or DELETE command.
**
** Bit 0 of the returned mask is set if the left-most column of the
** table may be accessed using an [old|new].<col> reference. Bit 1 is set if
** the second leftmost column value is required, and so on. If there
** are more than 32 columns in the table, and at least one of the columns
** with an index greater than 32 may be accessed, 0xffffffff is returned.
**
** It is not possible to determine if the old.rowid or new.rowid column is 
** accessed by triggers. The caller must always assume that it is.
**
** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned
** applies to the old.* table. If 1, the new.* table.
**
** Parameter tr_tm must be a mask with one or both of the TRIGGER_BEFORE
** and TRIGGER_AFTER bits set. Values accessed by BEFORE triggers are only
** included in the returned mask if the TRIGGER_BEFORE bit is set in the
** tr_tm parameter. Similarly, values accessed by AFTER triggers are only
** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm.
*/

        private static uint sqlite3TriggerColmask(
            Parse pParse, /* Parse context */
            Trigger pTrigger, /* List of triggers on table pTab */
            ExprList pChanges, /* Changes list for any UPDATE OF triggers */
            int isNew, /* 1 for new.* ref mask, 0 for old.* ref mask */
            int tr_tm, /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
            Table pTab, /* The table to code triggers from */
            int orconf /* Default ON CONFLICT policy for trigger steps */
            )
        {
            int op = pChanges != null ? TokenKeyword.TK_UPDATE : TokenKeyword.TK_DELETE;
            uint mask = 0;
            Trigger p;

            Debug.Assert(isNew == 1 || isNew == 0);
            for (p = pTrigger; p != null; p = p.pNext)
            {
                if (p.op == op && (tr_tm & p.tr_tm) != 0
                    && checkColumnOverlap(p.pColumns, pChanges) != 0
                    )
                {
                    TriggerPrg pPrg;
                    pPrg = getRowTrigger(pParse, p, pTab, orconf);
                    if (pPrg != null)
                    {
                        mask |= pPrg.aColmask[isNew];
                    }
                }
            }

            return mask;
        }
#endif
        // * !SQLITE_OMIT_TRIGGER) */
    }
}